use std::{convert::TryFrom, io, net::{ SocketAddr, ToSocketAddrs}, sync::Arc};
use std::net::UdpSocket;

use crate::utils;

#[derive(PartialEq, Eq, Debug)]
pub enum MessageSource {
    User,
    Others,
}

pub struct ChatMessage {
    pub source: MessageSource,
    pub name: String,
    pub message: String,
    pub timestamp: String
}

pub struct Client {
    name: String,
    socket: Arc<UdpSocket>,
    target: Option<SocketAddr>,
    messages: Vec<ChatMessage>,
}

impl Client {
    pub fn new(name: String) -> Self {
        Self::from_addr(name, utils::new_local_addr())
    }

    pub fn from_addr<A: ToSocketAddrs>(name: String, addr: A) -> Self {
        let socket = UdpSocket::bind(addr).unwrap();
        socket.set_nonblocking(true).unwrap();
        Self {
            name,
            socket: Arc::new(socket),
            target: None,
            messages: Vec::new(),
        }
    }

    pub fn socket(&self) -> Arc<UdpSocket> {
        Arc::clone(&self.socket)
    }

    pub fn name(&self) -> String {
        self.name.clone()
    }

    pub fn messages(&self) -> &Vec<ChatMessage> {
        &self.messages
    }

    pub fn send_addr(&self) -> SocketAddr {
        self.socket.local_addr().unwrap()
    }

    pub fn recv_bytes(&mut self, bytes: Vec<u8>) {
        if let Ok(pak) = moyu::Packet::try_from(&bytes[..]) {
            self.recv_packet(pak);
        }
    }

    pub fn recv_packet(&mut self, packet: moyu::Packet) {
        if packet.get_user_name() != self.name {
            let msg = ChatMessage {
                source: MessageSource::Others,
                name: packet.get_user_name().into(),
                message: packet.get_message().into(),
                timestamp: utils::timestamp(),
            };
    
            self.messages.push(msg);
        }
    }

    pub fn send_message(&mut self, message: &str) -> Result<(), &'static str> {
        let pak = moyu::Packet::new(&self.name, moyu::protocol::Operator::SendMessage, message);
        if let Err(msg) = self.send(pak.encode()) {
            Err(msg)
        } else {
            self.messages.push(ChatMessage {
                source: MessageSource::User,
                name: self.name.clone(),
                message: message.into(),
                timestamp: utils::timestamp(),
            });

            Ok(())
        }
    }

    pub fn tell(&self, name: &str, msg: &str) -> Result<(), &'static str>{
        let pak = moyu::Packet::new(&self.name, moyu::protocol::Operator::Tell, &format!("{}:{}", name, msg)).encode();

        self.send(pak)
    }

    pub fn query(&self) -> Result<(), &'static str> {
        let pak = moyu::Packet::new(&self.name, moyu::protocol::Operator::Query, "").encode();

        self.send(pak)
    }

    fn send(&self, pak: Vec<u8>) -> Result<(), &'static str> {
        if let Some(target) = self.target {
            if let Err(_) = self.socket.send_to(&pak[..], target) {
                Err("Send failed")
            } else {
                Ok(())
            }
        } else {
            return Err("Target not set");
        }
    }

    pub fn join(&mut self, target: SocketAddr, recv: SocketAddr) -> io::Result<()> {
        self.target.replace(target);

        let request = moyu::Packet::new(&self.name, moyu::protocol::Operator::Request, &recv.port().to_string());

        self.socket.send_to(&request.encode()[..], self.target.unwrap())?;
   
        Ok(())
    }

    pub fn connect(&mut self, target: SocketAddr) {
        self.target.replace(target);
    }

    pub fn disconnect(&mut self) {
        self.target.take();
    }

    pub fn exit(&self) {
        let pak = moyu::Packet::new(&self.name, moyu::protocol::Operator::Request, "EXIT").encode();

        if let Some(target) = self.target {
            self.socket.send_to(&pak[..], target).unwrap();
        }
    }
}

impl Drop for Client {
    fn drop(&mut self) {
        self.exit();
    }
}